// ignore_for_file: non_constant_identifier_names, constant_identifier_names

import 'package:flujs/extension/console.dart';
import 'package:flujs/flujs.dart';
import 'package:web_socket_channel/web_socket_channel.dart';

final wsCode = '''
const $WS_MAP = {};
function WebSocket(url, protocols) {
  this.binaryType = null;
  this.bufferedAmount = null; // readonly
  this.extensions = null; // readonly
  this.onclose = null;
  this.onerror = null;
  this.onmessage = null;
  this.onopen = null;
  this.protocol = null;
  this.readyState = WebSocket.CONNECTING;
  this.url = url;
  this._listeners = {};
  this._id = $WS_NEW(url, protocols);
  $WS_MAP[this._id] = this
}
WebSocket.CONNECTING = 0;
WebSocket.OPEN = 1;
WebSocket.CLOSING = 2;
WebSocket.CLOSED = 3;

WebSocket.prototype.close = function(code, reason) {
  this.readyState = WebSocket.CLOSING;
  $WS_CLOSE(this._id, code, reason);
}
WebSocket.prototype.send = function(data) {
  console.log("[WebSocket.protype.send]", data);
  $WS_SEND(this._id, data).then(console.log('send succeed!')).catch((e) => console.error('send failed:', e));
}
WebSocket.prototype.addEventListener = function(type, callback) {
  this._listeners[type] = callback;
  if (type == 'open') {
    this.onopen = callback;
  } else if (type == 'message') {
    this.onmessage = callback;
  } else if (type == 'close') {
    this.onclose = callback;
  } else if (type == 'error') {
    this.onerror = callback;
  }
}

''';
const WS_MAP = '__FLUJS_WS__';
const WS_NEW = '__FLUJS_WS_NEW__';
const WS_CLOSE = '__FLUJS_WS_CLOSE__';
const WS_SEND = '__FLUJS_WS_SEND__';

final WSCloseNormally = WSCloseReason(1000, 'normal close');
final WSCloseEndpointLeave = WSCloseReason(1001, 'endpoint leave');
final WSCloseWrongProtocol = WSCloseReason(1002, 'wrong protocol');
final WSCloseUnsupportDataType = WSCloseReason(1003, 'unsupport data type');
final WSCloseAbnormally = WSCloseReason(1006, 'close abnormally');

class WebsocketExtension extends JSFExtension {
  WebsocketExtension(super.context);
  Map<String, WebSocketChannel> channelMap = {};
  int channelCount = 0;

  @override
  load() {
    super.load();
    _start();
  }

  _start() {
    eval(wsCode, sourceURL: "WebSocket");

    addInterface(WS_NEW, (args) {
      String? url = args[0];
      if (url == null || url.isEmpty) {
        return Exception('Invalid arguments');
      }
      final wsUrl = Uri.parse(url);
      final channel = WebSocketChannel.connect(wsUrl);
      channelCount++;
      channelMap[channelCount.toString()] = channel;
      _waitForReady(channel, channelCount.toString());
      return channelCount;
    });

    addInterface(WS_CLOSE, (args) {
      String? channelId = args[0];
      if (channelId == null) {
        return Exception('Invalid arguments');
      }
      if (!channelMap.containsKey(channelId)) {
        return Exception('Invalid channel id');
      }
      int? code = args[1];
      String? reason = args[2];
      final channel = channelMap[channelId];
      channel!.sink.close(
          code ?? WSCloseNormally.code, reason ?? WSCloseNormally.reason);
    });

    addInterface(WS_SEND, (args) {
      Console.info("[WS_SEND] ${args.toString()}");
      dynamic channelId = args[0];
      if (channelId == null) {
        return ArgumentError('Invalid arguments');
      }
      channelId = channelId.toString();
      if (!channelMap.containsKey(channelId)) {
        return ArgumentError('Invalid channel id');
      }
      final channel = channelMap[channelId];
      String? message = args[1];
      if (message == null) {
        return ArgumentError('Invalid message');
      }
      Console.info("[WS_SEND] $channel, $channelId, $message");
      channel?.sink.add(message);
    });
  }

  _waitForReady(WebSocketChannel channel, String channelId) async {
    await channel.ready;
    context.eval('''
const __WS__ = $WS_MAP[$channelId];
if (__WS__ && __WS__.onopen != undefined) {
  __WS__.readState = WebSocket.OPEN;
  __WS__.onopen();
}
''');
    channel.stream.listen((message) {
      Console.info("[ws.onmessage] $message");
      context.eval('''
const __WS__ = $WS_MAP[$channelId];
console.log('[ws] ',__WS__)
if (__WS__ && __WS__.onmessage != undefined) {
  __WS__.onmessage({data: $message});
}
''');
    }, onDone: () {
      Console.info("[ws.ondone]");
      context.eval('''
const __WS__ = $WS_MAP[$channelId];
if (__WS__ && __WS__.onerror != undefined) {
  __WS__.readState = WebSocket.CLOSING;
  __WS__.onclose();
}
''');
    }, onError: (error) {
      Console.error("[ws.onerror] $error");
      context.eval('''
const __WS__ = $WS_MAP[$channelId];
if (__WS__ && __WS__.onerror != undefined) {
  __WS__.onerror({data: $error});
}
''');
    });
  }
}

class WSCloseReason {
  final int code;
  final String reason;
  WSCloseReason(this.code, this.reason);
}
